Ovládněte middleware FastAPI od základu. Tento hloubkový průvodce pokrývá vlastní middleware, autentizaci, protokolování, zpracování chyb a osvědčené postupy pro budování robustních API.
Python FastAPI Middleware: Komplexní průvodce zpracováním požadavků a odpovědí
Ve světě moderního vývoje webu jsou výkon, zabezpečení a udržovatelnost prvořadé. Rámec Pythonu FastAPI si rychle získal oblibu pro svou neuvěřitelnou rychlost a funkce usnadňující práci vývojářům. Jednou z jeho nejvýkonnějších, ale někdy nepochopených funkcí je middleware. Middleware funguje jako klíčový článek v řetězci zpracování požadavků a odpovědí, což umožňuje vývojářům provádět kód, upravovat data a vynucovat pravidla předtím, než požadavek dosáhne svého cíle nebo než se odpověď odešle zpět klientovi.
Tento komplexní průvodce je určen pro globální publikum vývojářů, od těch, kteří s FastAPI teprve začínají, až po zkušené profesionály, kteří chtějí prohloubit své znalosti. Prozkoumáme základní koncepty middleware, ukážeme, jak vytvářet vlastní řešení, a projdeme praktickými, reálnými případy použití. Do konce budete vybaveni k využívání middleware k vytváření robustnějších, bezpečnějších a efektivnějších API.
Co je Middleware v kontextu webových rámců?
Než se ponoříme do kódu, je nezbytné porozumět konceptu. Představte si cyklus požadavků a odpovědí vaší aplikace jako potrubí nebo montážní linku. Když klient odešle požadavek do vašeho API, nedostane se okamžitě k logice vašeho koncového bodu. Místo toho prochází řadou kroků zpracování. Podobně, když váš koncový bod generuje odpověď, prochází zpět těmito kroky, než se dostane ke klientovi. Komponenty middleware jsou právě těmito kroky v potrubí.
Populární analogií je cibulový model. Jádrem cibule je obchodní logika vaší aplikace (koncový bod). Každá vrstva cibule obklopující jádro je část middleware. Požadavek se musí probojovat každou vnější vrstvou, aby se dostal k jádru, a odpověď se vrací zpět stejnými vrstvami. Každá vrstva může zkoumat a upravovat požadavek na cestě dovnitř a odpověď na cestě ven.
V podstatě je middleware funkce nebo třída, která má přístup k objektu požadavku, objektu odpovědi a dalšímu middleware v cyklu požadavků a odpovědí aplikace. Mezi jeho primární účely patří:
- Provádění kódu: Provádět akce pro každý příchozí požadavek, například protokolování nebo monitorování výkonu.
- Úprava požadavku a odpovědi: Přidávat hlavičky, komprimovat těla odpovědí nebo transformovat datové formáty.
- Zkratování cyklu: Ukončit cyklus požadavků a odpovědí dříve. Middleware pro autentizaci může například blokovat neověřený požadavek ještě předtím, než se dostane do zamýšleného koncového bodu.
- Řízení globálních záležitostí: Zpracovávat průřezové záležitosti, jako je zpracování chyb, CORS (sdílení prostředků napříč doménami) a správa relací na centralizovaném místě.
FastAPI je postaven na sadě nástrojů Starlette, která poskytuje robustní implementaci standardu ASGI (Asynchronous Server Gateway Interface). Middleware je základní koncept v ASGI, což z něj činí prvotřídního občana v ekosystému FastAPI.
Nejjednodušší forma: FastAPI Middleware s dekorátorem
FastAPI poskytuje jednoduchý způsob, jak přidat middleware pomocí dekorátoru @app.middleware("http"). To je ideální pro jednoduchou, soběstačnou logiku, která se musí spouštět pro každý požadavek HTTP.
Vytvořme klasický příklad: middleware pro výpočet doby zpracování pro každý požadavek a jeho přidání do hlaviček odpovědi. To je neuvěřitelně užitečné pro monitorování výkonu.
Příklad: Middleware pro dobu zpracování
Nejprve se ujistěte, že máte nainstalované FastAPI a server ASGI jako Uvicorn:
pip install fastapi uvicorn
Nyní si napišme kód do souboru s názvem main.py:
import time
from fastapi import FastAPI, Request
app = FastAPI()
# Definujte funkci middleware
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# Zaznamenat počáteční čas, když požadavek přijde
start_time = time.time()
# Přejít na další middleware nebo koncový bod
response = await call_next(request)
# Vypočítat dobu zpracování
process_time = time.time() - start_time
# Přidat vlastní hlavičku do odpovědi
response.headers["X-Process-Time"] = str(process_time)
return response
@app.get("/")
async def root():
# Simulovat nějakou práci
time.sleep(0.5)
return {"message": "Hello, World!"}
Chcete-li tuto aplikaci spustit, použijte příkaz:
uvicorn main:app --reload
Nyní, pokud odešlete požadavek na http://127.0.0.1:8000 pomocí nástroje jako cURL nebo klienta API jako Postman, uvidíte v odpovědi novou hlavičku X-Process-Time s hodnotou přibližně 0,5 sekundy.
Demontáž kódu:
@app.middleware("http"): Tento dekorátor registruje naši funkci jako kus HTTP middleware.async def add_process_time_header(request: Request, call_next):: Funkce middleware musí být asynchronní. Přijímá příchozí objektRequesta speciální funkcicall_next.response = await call_next(request): Toto je nejdůležitější řádek.call_nextpředává požadavek dalšímu kroku v potrubí (buď dalšímu middleware, nebo skutečné operaci cesty). Musíte tento hovor await. Výsledkem je objektResponsegenerovaný koncovým bodem.response.headers[...] = ...: Po přijetí odpovědi z koncového bodu ji můžeme upravit, v tomto případě přidáním vlastní hlavičky.return response: Nakonec se upravená odpověď vrátí, aby byla odeslána klientovi.
Vytváření vlastního middleware s třídami
Zatímco přístup s dekorátorem je jednoduchý, může se stát omezujícím pro složitější scénáře, zejména když vaše middleware vyžaduje konfiguraci nebo potřebuje spravovat nějaký interní stav. Pro tyto případy FastAPI (prostřednictvím Starlette) podporuje middleware založený na třídách pomocí BaseHTTPMiddleware.
Přístup založený na třídách nabízí lepší strukturu, umožňuje injekci závislostí ve svém konstruktoru a je obecně udržitelnější pro složitou logiku. Jádrová logika sídlí v asynchronní metodě dispatch.
Příklad: Middleware pro ověřování s klíčem API založeným na třídě
Pojďme vytvořit praktičtější middleware, který zabezpečí naše API. Zkontroluje konkrétní hlavičku X-API-Key, a pokud klíč není přítomen nebo je neplatný, okamžitě vrátí chybovou odpověď 403 Forbidden. Toto je příklad „zkratování“ požadavku.
V main.py:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import Response
# Seznam platných klíčů API. Ve skutečné aplikaci by to pocházelo z databáze nebo zabezpečeného trezoru.
VALID_API_KEYS = ["my-super-secret-key", "another-valid-key"]
class APIKeyMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
api_key = request.headers.get("X-API-Key")
if api_key not in VALID_API_KEYS:
# Zkratovat požadavek a vrátit chybovou odpověď
return JSONResponse(
status_code=403,
content={"detail": "Forbidden: Neplatný nebo chybějící klíč API"}
)
# Pokud je klíč platný, pokračujte s požadavkem
response = await call_next(request)
return response
app = FastAPI()
# Přidejte middleware do aplikace
app.add_middleware(APIKeyMiddleware)
@app.get("/")
async def root():
return {"message": "Vítejte v zabezpečené zóně!"}
Nyní, když tuto aplikaci spustíte:
- Požadavek bez hlavičky
X-API-Key(nebo se špatnou hodnotou) obdrží stavový kód 403 a chybovou zprávu JSON. - Požadavek s hlavičkou
X-API-Key: my-super-secret-keybude úspěšný a obdrží odpověď 200 OK.
Tento vzor je extrémně výkonný. Kód koncového bodu na / nemusí nic vědět o validaci klíče API; tato záležitost je zcela oddělena do vrstvy middleware.
Běžné a výkonné případy použití pro middleware
Middleware je dokonalý nástroj pro zpracování průřezových záležitostí. Prozkoumejme některé z nejběžnějších a nejvlivnějších případů použití.
1. Centralizované protokolování
Komplexní protokolování je pro produkční aplikace nezbytné. Middleware vám umožňuje vytvořit jediný bod, kde protokolujete kritické informace o každém požadavku a odpovídající odpovědi.
Příklad protokolování Middleware:
import logging
from fastapi import FastAPI, Request
import time
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
@app.middleware("http")
async def logging_middleware(request: Request, call_next):
start_time = time.time()
# Zaznamenat podrobnosti požadavku
logger.info(f"Příchozí požadavek: {request.method} {request.url.path}")
response = await call_next(request)
process_time = time.time() - start_time
# Zaznamenat podrobnosti odpovědi
logger.info(f"Stav odpovědi: {response.status_code} | Doba zpracování: {process_time:.4f}s")
return response
Tento middleware zaznamenává metodu a cestu požadavku na cestě dovnitř a stavový kód odpovědi a celkovou dobu zpracování na cestě ven. To poskytuje neocenitelný přehled o provozu vaší aplikace.
2. Globální zpracování chyb
Ve výchozím nastavení bude nezachycená výjimka ve vašem kódu mít za následek 500 Interní chyba serveru, což může potenciálně odhalit trasování zásobníku a podrobnosti implementace klientovi. Middleware pro globální zpracování chyb může zachytit všechny výjimky, protokolovat je pro interní kontrolu a vrátit standardizovanou, uživatelsky přívětivou chybovou odpověď.
Příklad Middleware pro zpracování chyb:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import logging
logger = logging.getLogger(__name__)
app = FastAPI()
@app.middleware("http")
async def error_handling_middleware(request: Request, call_next):
try:
return await call_next(request)
except Exception as e:
logger.error(f"Došlo k nezachycené chybě: {e}", exc_info=True)
return JSONResponse(
status_code=500,
content={"detail": "Došlo k interní chybě serveru. Zkuste to prosím znovu později."}
)
@app.get("/error")
async def cause_error():
return 1 / 0 # Tím se vyvolá ZeroDivisionError
S tímto middleware bude požadavek na /error již nespadne server ani neodhalí trasování zásobníku. Místo toho elegantně vrátí stavový kód 500 s čistým tělem JSON, zatímco celá chyba je protokolována na straně serveru, aby ji vývojáři mohli prozkoumat.
3. CORS (Cross-Origin Resource Sharing)
Pokud je vaše frontendová aplikace obsluhována z jiné domény, protokolu nebo portu než backend FastAPI, prohlížeče zablokují požadavky kvůli zásadám stejného původu. CORS je mechanismus pro zmírnění těchto zásad. FastAPI poskytuje vyhrazený, vysoce konfigurovatelný CORSMiddleware pro tento přesný účel.
Příklad konfigurace CORS:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Definujte seznam povolených zdrojů. Použijte "*" pro veřejná API, ale buďte konkrétní pro lepší zabezpečení.
origins = [
"http://localhost:3000",
"https://my-production-frontend.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True, # Umožnit zahrnutí cookies v požadavcích napříč doménami
allow_methods=["*"], # Povolit všechny standardní metody HTTP
allow_headers=["*"], # Povolit všechny hlavičky
)
Toto je jedna z prvních částí middleware, které pravděpodobně přidáte do jakéhokoli projektu s odděleným frontendem, což usnadňuje správu zásad napříč doménami z jednoho centrálního umístění.
4. Komprese GZip
Komprese odpovědí HTTP může výrazně snížit jejich velikost, což vede k rychlejší době načítání pro klienty a nižším nákladům na šířku pásma. FastAPI obsahuje GZipMiddleware, který to automaticky zpracovává.
Příklad GZip Middleware:
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
# Přidejte middleware GZip. Můžete nastavit minimální velikost pro kompresi.
app.add_middleware(GZipMiddleware, minimum_size=1000)
@app.get("/")
async def root():
# Tato odpověď je malá a nebude gzipped.
return {"message": "Hello World"}
@app.get("/large-data")
async def large_data():
# Tato velká odpověď bude automaticky gzipped pomocí middleware.
return {"data": "a_very_long_string..." * 1000}
S tímto middleware bude jakákoli odpověď větší než 1000 bajtů komprimována, pokud klient naznačí, že akceptuje kódování GZip (což dělají prakticky všechny moderní prohlížeče a klienti).
Pokročilé koncepty a osvědčené postupy
Když se stanete zběhlejšími v middleware, je důležité porozumět některým nuancím a osvědčeným postupům, abyste mohli psát čistý, efektivní a předvídatelný kód.
1. Pořadí middleware je důležité!
Toto je nejdůležitější pravidlo, které je třeba pamatovat. Middleware se zpracovává v pořadí, v jakém je přidáno do aplikace. První přidaný middleware je nejvzdálenější vrstva „cibule“.
Zvažte toto nastavení:
app.add_middleware(ErrorHandlingMiddleware) # Nejvzdálenější
app.add_middleware(LoggingMiddleware)
app.add_middleware(AuthenticationMiddleware) # Nejvnitřnější
Tok požadavku by byl:
ErrorHandlingMiddlewarepřijímá požadavek. Zabalí jehocall_nextdo blokutry...except.- Volá
nexta předává požadavek naLoggingMiddleware. LoggingMiddlewarepřijímá požadavek, protokoluje jej a volánext.AuthenticationMiddlewarepřijímá požadavek, ověřuje pověření a volánext.- Požadavek nakonec dosáhne koncového bodu.
- Koncový bod vrací odpověď.
AuthenticationMiddlewarepřijímá odpověď a předává ji nahoru.LoggingMiddlewarepřijímá odpověď, protokoluje ji a předává ji nahoru.ErrorHandlingMiddlewarepřijímá konečnou odpověď a vrací ji klientovi.
Toto pořadí je logické: obsluha chyb je zvenku, takže může zachytit chyby z jakékoli následné vrstvy, včetně ostatních middleware. Vrstva autentizace je hluboko uvnitř, takže se neobtěžujeme protokolováním nebo zpracováním požadavků, které budou stejně odmítnuty.
2. Předávání dat s request.state
Někdy potřebuje middleware předat informace do koncového bodu. Middleware pro ověřování může například dekódovat JWT a extrahovat ID uživatele. Jak může toto ID uživatele zpřístupnit funkci operace cesty?
Špatný způsob je přímo upravovat objekt požadavku. Správný způsob je použít objekt request.state. Jedná se o jednoduchý, prázdný objekt, který je poskytnut pro tento přesný účel.
Příklad: Předávání uživatelských dat z Middleware
# Ve vaší metodě dispatch middleware pro ověřování:
# ... po ověření tokenu a dekódování uživatele ...
user_data = {"id": 123, "username": "global_dev"}
request.state.user = user_data
response = await call_next(request)
# Ve vašem koncovém bodě:
@app.get("/profile")
async def get_user_profile(request: Request):
current_user = request.state.user
return {"profile_for": current_user}
Tím se zachová čistota logiky a zabrání se znečištění jmenného prostoru objektu Request.
3. Úvahy o výkonu
Zatímco middleware je výkonný, každá vrstva přidává malé množství režie. Pro vysoce výkonné aplikace mějte na paměti tyto body:
- Udržujte jej štíhlý: Logika middleware by měla být co nejrychlejší a nejefektivnější.
- Buďte asynchronní: Pokud vaše middleware potřebuje provádět operace I/O (jako je kontrola databáze), ujistěte se, že je plně
async, aby nedošlo k zablokování smyčky událostí serveru. - Použijte se záměrem: Nepřidávejte middleware, který nepotřebujete. Každý z nich přidává hloubku zásobníku volání a dobu zpracování.
4. Testování vašeho middleware
Middleware je kritickou součástí logiky vaší aplikace a měl by být důkladně otestován. TestClient FastAPI to usnadňuje. Můžete psát testy, které odesílají požadavky se a bez požadovaných podmínek (např. se a bez platného klíče API) a ujišťují se, že se middleware chová podle očekávání.
Příklad testu pro APIKeyMiddleware:
from fastapi.testclient import TestClient
from .main import app # Importujte svou aplikaci FastAPI
client = TestClient(app)
def test_request_without_api_key_is_forbidden():
response = client.get("/")
assert response.status_code == 403
assert response.json() == {"detail": "Forbidden: Neplatný nebo chybějící klíč API"}
def test_request_with_valid_api_key_is_successful():
headers = {"X-API-Key": "my-super-secret-key"}
response = client.get("/", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": "Vítejte v zabezpečené zóně!"}
Závěr
Middleware FastAPI je základní a výkonný nástroj pro každého vývojáře, který vytváří moderní webová API. Poskytuje elegantní a znovupoužitelný způsob, jak řešit průřezové záležitosti, oddělit je od vaší základní obchodní logiky. Zachycením a zpracováním každého požadavku a odpovědi vám middleware umožňuje implementovat robustní protokolování, centralizované zpracování chyb, přísné bezpečnostní zásady a vylepšení výkonu, jako je komprese.
Od jednoduchého dekorátoru @app.middleware("http") až po sofistikovaná řešení založená na třídách máte flexibilitu při výběru správného přístupu pro vaše potřeby. Pochopením základních konceptů, běžných případů použití a osvědčených postupů, jako je řazení middleware a správa stavu, můžete vytvářet čistší, bezpečnější a vysoce udržovatelné aplikace FastAPI.
Teď je na vás. Začněte integrovat vlastní middleware do svého příštího projektu FastAPI a odemkněte novou úroveň kontroly a elegance v návrhu vašeho API. Možnosti jsou obrovské a zvládnutí této funkce z vás nepochybně udělá efektivnějšího a efektivnějšího vývojáře.